/*
 * Copyright (C) 2006-2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections.Generic;

using DCSharp.Backend.Connections;
using DCSharp.Backend.Managers;
using DCSharp.Backend.Objects;
using DCSharp.Hashing;
using DCSharp.Logging;
using DCSharp.Settings;

using Nmdc = DCSharp.Backend.Protocols.Nmdc;

namespace DCSharp
{
	public enum RuntimeError
	{
		ServerStart
	}

	public class RuntimeErrorEventArgs : EventArgs
	{
		public RuntimeErrorEventArgs(RuntimeError error)
		{
			this.error = error;
		}

		public RuntimeError Error
		{
			get { return error; }
		}
		private RuntimeError error;
	}

	/// <summary>
	/// 
	/// </summary>
	public class Runtime
	{
		public event EventHandler<RuntimeErrorEventArgs> Error;

		private static Logger log = LogManager.GetLogger("Runtime");

		private IRuntimeSettings settings;
		private IConnectionSettings connectionSettings;

		private HubManager hubManager;
		private ConnectionManager connectionManager;
		private SearchManager searchManager;
		private ShareManager shareManager;
		private DownloadManager downloadManager;
		private UploadManager uploadManager;

		private ProtocolHelper protocolHelper;
		private Nmdc.UdpProtocol udpProtocol;

		#region Constructors

		public Runtime(IRuntimeSettings settings, IConnectionSettings connectionSettings,
			HashStore hashStore, string dataDir, IEnumerable<ShareInfo> shared)
		{
			if (settings == null)
			{
				throw new ArgumentNullException("settings");
			}
			if (connectionSettings == null)
			{
				throw new ArgumentNullException("connectionSettings");
			}
			this.settings = settings;
			this.connectionSettings = connectionSettings;

			protocolHelper = new ProtocolHelper(this);

			// Managers
			hubManager = new HubManager(protocolHelper, settings);
			connectionManager = new ConnectionManager(hubManager, protocolHelper,
				settings);
			searchManager = new SearchManager(hubManager);

			shareManager = new ShareManager(settings, hashStore, dataDir, shared);
			uploadManager = new UploadManager(settings, connectionManager,
				shareManager);

			downloadManager = new DownloadManager(settings, connectionManager,
				hashStore);

			// Other
			udpProtocol = new Nmdc.UdpProtocol(protocolHelper, protocolHelper);
		}

		#endregion

		#region Properties

		/// <value>
		/// The general settings.
		/// </value>
		public IRuntimeSettings Settings
		{
			get { return settings; }
		}

		/// <value>
		/// The connection settings.
		/// </value>
		public IConnectionSettings ConnectionSettings
		{
			get { return connectionSettings; }
		}

		/// <value>
		/// 
		/// </value>
		public HubManager HubManager
		{
			get { return hubManager; }
		}

		/// <value>
		/// 
		/// </value>
		public ConnectionManager ConnectionManager
		{
			get { return connectionManager; }
		}

		/// <value>
		/// 
		/// </value>
		public SearchManager SearchManager
		{
			get { return searchManager; }
		}

		/// <value>
		/// 
		/// </value>
		public ShareManager ShareManager
		{
			get { return shareManager; }
		}

		/// <value>
		/// 
		/// </value>
		public DownloadManager DownloadManager
		{
			get { return downloadManager; }
		}

		/// <value>
		/// 
		/// </value>
		public UploadManager UploadManager
		{
			get { return uploadManager; }
		}

		#endregion

		#region Methods

		/// <summary>
		/// Shuts down all the managers and stops listening.
		/// </summary>
		public void Shutdown()
		{
			downloadManager.Dispose();
			shareManager.SaveFileList();

			StopServers();
			UdpServer.CloseSockets();
		}

		#region Server

		/// <summary>
		/// Starts listening for incoming connections and search results.
		/// </summary>
		/// <remarks>Nothing is done if listening has been disabled in the settings.</remarks>
		/// <returns>True if no errors occured; otherwise, false.</returns>
		public bool StartServers()
		{
			StopServers();
			if (ConnectionSettings.SupportsIncoming)
			{
				try
				{
					TcpServer.StartListening(ConnectionSettings.Port,
						connectionManager.HandleIncomingConnection);

					UdpServer.StartListening(ConnectionSettings.Port, udpProtocol);
				}
				catch(System.Net.Sockets.SocketException e)
				{
					OnError(RuntimeError.ServerStart, e);
					return false;
				}
			}
			return true;
		}

		/// <summary>
		/// Stops listening.
		/// </summary>
		public void StopServers()
		{
			TcpServer.StopListening();
			UdpServer.StopListening();
		}

		#endregion

		/// <summary>
		/// Connects to a hub.
		/// </summary>
		/// <param name="favoriteHub">The hub to connect to.</param>
		/// <returns>The existing or a new connection to the hub.</returns>
		public HubConnection ConnectToHub(FavoriteHubInfo favoriteHub)
		{
			// TODO: Move to HubManager/FavoriteHubManager?
			if (favoriteHub == null)
			{
				throw new ArgumentNullException("favoriteHub");
			}

			Uri uri = StringUtil.CreateUri(favoriteHub.Hostname);

			HubConnection hub = hubManager.GetHub(uri);
			if (hub != null)
			{
				return hub;
			}

			// Create the user for this hub
			LocalIdentity user = settings.LocalIdentity;

			if (!String.IsNullOrEmpty(favoriteHub.Nick) ||
				!String.IsNullOrEmpty(favoriteHub.UserDescription))
			{
				user = (LocalIdentity)settings.LocalIdentity.Clone();

				if (!String.IsNullOrEmpty(favoriteHub.Nick))
				{
					user.Nick = favoriteHub.Nick;
				}
				if (!String.IsNullOrEmpty(favoriteHub.UserDescription))
				{
					user.Description = favoriteHub.UserDescription;
				}
			}
			return hubManager.ConnectTo(uri, user, favoriteHub.Password);
		}

		protected virtual void OnError(RuntimeError error, Exception e)
		{
			log.Error(error.ToString(), e);
			if (Error != null)
			{
				Error(null, new RuntimeErrorEventArgs(error));
			}
		}

		#endregion
	}
}
